#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.IO;
using System.Threading;
using BitwiseOre.IO;
using BitwiseOre.Properties;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.JetBrains;
using BitwiseOre.SharedMemory.Utils;

namespace BitwiseOre.SharedMemory
{
    /// <summary>
    /// Provides a factory for establishing interprocess communication connections
    /// to an available listener over shared memory.
    /// </summary>
    [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
    public sealed class SharedMemoryFactory : IDisposable, IConnectionFactory
    {
        private static readonly string UnableToConnectMessage = Resources.SharedMemoryFactory_UnableToConnectMessage;
        private static readonly string ListnerNotFoundMessage = Resources.SharedMemoryFactory_ListnerNotFoundMessage;

        private const int DefaultConnectTimeout = 20000;

        private readonly IDisposer<SharedMemoryFactory> _disposer;
        private readonly IConnector _connector;
        private readonly Func<Guid, SharedMemoryStream> _streamFactory;
        private readonly IAsyncResultEnder<SharedMemoryStream> _asyncResultEnder ;

        /// <summary>
        /// Creates a new <see cref="SharedMemoryFactory"/> in a given <see cref="SharedMemoryContext"/>
        /// </summary>
        /// <param name="name">
        ///     The name of the listener to which to establish connection with this factory.
        /// </param>
        /// <remarks>
        ///     This overload of <c>Create</c> uses the default <see cref="SharedMemoryContext"/>.
        /// </remarks>
        /// <exception cref="ListenerNotFoundException">
        ///     No <see cref="SharedMemoryListener"/> named <paramref name="name"/> could
        ///     be found.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="name"/> is <c>null</c>.
        /// </exception>
        /// <returns>
        ///     The new <see cref="SharedMemoryFactory"/>.
        /// </returns>
        public static SharedMemoryFactory Create(string name)
        {
            return Create(new SharedMemoryContext(), name);
        }

        /// <summary>
        /// Creates a new <see cref="SharedMemoryFactory"/> in a given <see cref="SharedMemoryContext"/>
        /// </summary>
        /// <param name="context">
        ///     The context in which to create the new <see cref="SharedMemoryFactory"/>.
        /// </param>
        /// <param name="name">
        ///     The name of the listener to which to establish connection with this factory.
        /// </param>
        /// <exception cref="ListenerNotFoundException">
        ///     No <see cref="SharedMemoryListener"/> named <paramref name="name"/> could
        ///     be found.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="context"/> or <paramref name="name"/> is <c>null</c>.
        /// </exception>
        /// <returns>
        ///     The new <see cref="SharedMemoryFactory"/>.
        /// </returns>
        public static SharedMemoryFactory Create([NotNull] SharedMemoryContext context, [NotNull] string name)
        {
            if (context == null) throw new ArgumentNullException("context");
            if (name == null) throw new ArgumentNullException("name");
            try
            {
                return context.CreateFactory(name);
            }
            catch (WaitHandleCannotBeOpenedException e)
            {
                var message = string.Format(ListnerNotFoundMessage, name);
                throw new ListenerNotFoundException(message, name, e);
            }
            catch (FileNotFoundException e)
            {
                var message = string.Format(ListnerNotFoundMessage, name);
                throw new ListenerNotFoundException(message, name, e);
            }
        }

        /// <summary>
        /// Establishes a connection to the <see cref="SharedMemoryListener"/>
        /// with which this factory is paired.
        /// </summary>
        /// <returns>
        ///     A <see cref="SharedMemoryStream"/> that represents one side
        ///     of the interprocess communication channel established with the
        ///     named listner.
        /// </returns>
        public DisconnectableStream Connect()
        {
            return EndConnect(BeginConnect(null, null));
        }

        internal SharedMemoryFactory(IDisposer<SharedMemoryFactory> disposer, IConnector connector,
                                     Func<Guid, SharedMemoryStream> streamFactory,
                                     IAsyncResultEnder<SharedMemoryStream> asyncResultEnder)
        {
            _disposer = disposer;
            _asyncResultEnder = asyncResultEnder;
            _connector = connector;
            _streamFactory = streamFactory;
            ConnectTimeout = DefaultConnectTimeout;
        }


        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _disposer.Dispose();
        }

        /// <summary>
        /// Begins an asynchronous operation to establish a connection with a
        /// <see cref="SharedMemoryListener"/> with which this factory is paired.
        /// </summary>
        /// <param name="callback">
        ///   An <see cref="AsyncCallback"/> that references the method to invoke when
        ///   the operation is complete.
        /// </param>
        /// <param name="state">
        ///   A user-defined object containing information about the connect operation.
        /// </param>
        /// <exception cref="UnableToConnectException">
        ///     The factory was unable to establish a connection with the specified listner.
        ///     This may be a transient problem that is resolved by external events.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryFactory"/> has been disposed.
        /// </exception>
        /// <returns>
        ///     An <see cref="IAsyncResult"/> that reference the asynchronous connect operation.
        /// </returns>
        public IAsyncResult BeginConnect(AsyncCallback callback, object state)
        {
            _disposer.ThrowIfDisposed();

            try
            {
                return _connector.Connect(TimeSpan.FromMilliseconds(ConnectTimeout), _streamFactory)
                    .WithAsyncCallback(callback, state);
            }
            catch (InvalidOperationException e)
            {
                throw new UnableToConnectException(UnableToConnectMessage, e);
            }
        }

        /// <summary>
        /// Completes an asynchronous operation to establish a connection with a
        /// <see cref="SharedMemoryListener"/>.
        /// </summary>
        /// <param name="asyncResult">
        ///     The asynchrounous operation to complete. Must be the return value
        ///     from <see cref="BeginConnect"/>.
        /// </param>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryFactory"/> has been disposed.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="asyncResult"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="asyncResult"/> is not a return value from a call to
        ///     <see cref="BeginConnect"/>.
        /// </exception>
        /// <exception cref="UnableToConnectException">
        ///     The factory was unable to establish a connection with the specified listner.
        ///     This may be a transient problem that is resolved by external events.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     The connection operation timed out.
        /// </exception>
        /// <returns>
        ///     A <see cref="SharedMemoryStream"/> that represents one side
        ///     of the interprocess communication channel established with the
        ///     named listner.
        /// </returns>
        public DisconnectableStream EndConnect(IAsyncResult asyncResult)
        {
            _disposer.ThrowIfDisposed();

            return _asyncResultEnder.EndTask(asyncResult,
                                      ae => new UnableToConnectException(UnableToConnectMessage, ae));
        }

        /// <summary>
        /// Gets or sets a value, in milliseconds, that represents the length
        /// of time that the factory will wait for a connection operation to
        /// complete before timing out.
        /// </summary>
        public int ConnectTimeout { get; set; }
    }
}